package com.arpa.wms.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.arpa.ntocc.common.common.exception.ServiceException;
import com.arpa.ntocc.common.common.util.UserUtil;
import com.arpa.wms.domain.dto.InventoryOperationDto;
import com.arpa.wms.domain.entity.*;
import com.arpa.wms.domain.enums.DeliveryStatusEnum;
import com.arpa.wms.domain.enums.InventoryRecordTypeEnum;
import com.arpa.wms.domain.enums.TempLocationTypeEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.Data;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

import static com.arpa.wms.domain.consts.NumConst.NUM_ONE;

/**
 * 奇门取消订单处理
 * @author liuxr
 * @date 2021年05月11日 15:25
 */
@Log4j2
@Service
public class OrderCancelService {

    @Autowired
    private IDeliveryService iDeliveryService;

    @Autowired
    private IDeliveryDistributionService iDeliveryDistributionService;

    @Autowired
    private InventoryOperationService inventoryOperationService;

    @Autowired
    private IInventoryRecordService inventoryRecordService;

    @Autowired
    private IInventoryService inventoryService;

    @Autowired
    private ILocationService iLocationService;

    /**
     * 订单取消处理
     * @author liuxr
     * @date 2021/11/23 10:12
     * @param deliveryCodes 订单号
     */
    @Transactional(rollbackFor = Exception.class)
    public Set<String> deliveryOrderCancel(List<String> deliveryCodes) {
        log.info("取消订单处理开始:订单号：{}", deliveryCodes);
        if(IterUtil.isEmpty(deliveryCodes)){
            return null;
        }
        List<Delivery> deliveryList = null;
         if(IterUtil.isNotEmpty(deliveryCodes)){
            deliveryList = iDeliveryService.list(Wrappers.lambdaQuery(Delivery.class)
                    .eq(Delivery::getGroupCode,UserUtil.getBranchCode())
                    .select(Delivery::getCode,Delivery::getStatus,Delivery::getWarehouseCode)
                    .in(Delivery::getCode, deliveryCodes)
                    .eq(Delivery::getIsCancel, NUM_ONE)
            );
            log.info("订单取消处理，获取已取消的订单:{}", JSONUtil.toJsonStr(deliveryList));
        }
        if (IterUtil.isNotEmpty(deliveryList)) {
            Set<String> cancelCodes = deliveryList.stream().map(Delivery::getCode).collect(Collectors.toSet());

            deliveryList = deliveryList.stream()
                   .filter(r-> !StrUtil.equals(r.getStatus(),DeliveryStatusEnum.VOID.getValue()) )
                   .collect(Collectors.toList());
            log.info("订单取消处理，需要处理的订单:{}", JSONUtil.toJsonStr(deliveryList));

            for (Delivery delivery : deliveryList) {
                //状态,0-新增。1-待分配，2-部分分配，3-已分配，4-已作废,5生成波次中，6待拣货，7待复核，8复核中，9待发货，10发货中，11发货完成
                //状态,0-新增。1-待分配，直接更新状态为作废
                if (StrUtil.equalsAny(delivery.getStatus(), DeliveryStatusEnum.ADD.getValue(), DeliveryStatusEnum.UN_DISTRIBUTION.getValue())) {
                    log.info("订单取消处理,订单【{}】状态为：{}，直接更新状态为作废",delivery.getCode(),delivery.getStatus());
                    // 更新订单的状态，改为作废
                    boolean bool = iDeliveryService.update(new UpdateWrapper<Delivery>().lambda()

                            .eq(Delivery::getGroupCode,UserUtil.getBranchCode())
                            .eq(Delivery::getCode, delivery.getCode())
                            .eq(Delivery::getIsCancel, NUM_ONE)
                            .set(Delivery::getStatus, DeliveryStatusEnum.VOID.getValue()));
                    log.info("订单取消处理,订单【{}】状态为：{}，直接更新状态为作废,更新结果：{}",delivery.getCode(),delivery.getStatus(),bool);
                }
                //状态,3-已分配，还原库存
                if (StrUtil.equalsAny(delivery.getStatus(), DeliveryStatusEnum.PART_DISTRIBUTION.getValue(), DeliveryStatusEnum.DISTRIBUTIONED.getValue())) {
                    log.info("订单取消处理,订单【{}】状态为：{}，先还原库存再更新状态为作废",delivery.getCode(),delivery.getStatus());
                    //returnInventory(delivery.getCode(), delivery.getStatus());
                    returnInventory(delivery.getCode(),delivery.getWarehouseCode(),true);
                    // 更新订单的状态，改为作废
                    iDeliveryService.update(new UpdateWrapper<Delivery>().lambda()
                            .eq(Delivery::getGroupCode,UserUtil.getBranchCode())
                            .eq(Delivery::getCode, delivery.getCode())
                            .eq(Delivery::getIsCancel, NUM_ONE)
                            .set(Delivery::getStatus, DeliveryStatusEnum.VOID.getValue()));
                }
                //状态,6待拣货，还原库存,修改拣货数量
                if (StrUtil.equalsAny(delivery.getStatus(), DeliveryStatusEnum.UN_PICKING.getValue())) {
                    log.info("订单取消处理,订单【{}】状态为：{}，先还原库存再更新状态为作废",delivery.getCode(),delivery.getStatus());
                    returnInventory(delivery.getCode(),delivery.getWarehouseCode(),true);
                    // 更新订单的状态，改为作废
                    iDeliveryService.update(new UpdateWrapper<Delivery>().lambda()
                            .eq(Delivery::getCode, delivery.getCode())
                            .eq(Delivery::getIsCancel, NUM_ONE)
                            .set(Delivery::getStatus, DeliveryStatusEnum.VOID.getValue()));
                    //TODO 更新拣货单取消数量
                }


                //状态,7待复核、8复核中、9待出库，到退拣库位
                if (StrUtil.equalsAny(delivery.getStatus(), DeliveryStatusEnum.UN_RECHECK.getValue(), DeliveryStatusEnum.RECHECKING.getValue(), DeliveryStatusEnum.UN_DELIVERY.getValue())) {
                    returnInventory(delivery.getCode(),delivery.getWarehouseCode(),false);
                    // 更新订单的状态，改为作废
                    iDeliveryService.update(new UpdateWrapper<Delivery>().lambda()
                            .eq(Delivery::getGroupCode,UserUtil.getBranchCode())
                            .eq(Delivery::getCode, delivery.getCode())
                            .eq(Delivery::getIsCancel, NUM_ONE)
                            .set(Delivery::getStatus, DeliveryStatusEnum.VOID.getValue()));
                }

            }
            return cancelCodes;
        }
        return null;
    }





    /**
     * 库存处理
     * 分配库存退回原库位；
     *
     * @param deliveryCode 取消的订单号
     * @param warehouseCode 订单所属仓库
     * @param needReturnSourceInventory 临时库存是否退回原库位，true：退回原库位，false：退回退拣库位
     * @author liuxr
     * @date 2021/6/2 13:32
     */
    private void returnInventory(String deliveryCode,String warehouseCode,boolean needReturnSourceInventory) {
        log.info("订单取消处理，部分分配、已分配订单返还库存，订单【{}】",deliveryCode);
        //setp1:根据订单号获取所有分配明细
        List<DeliveryDistribution> deliveryDistributions = iDeliveryDistributionService.list(Wrappers.lambdaQuery(DeliveryDistribution.class)
                .eq(DeliveryDistribution::getGroupCode,UserUtil.getBranchCode())
                .select(DeliveryDistribution::getCode,
                        DeliveryDistribution::getDeliveryCode,
                        DeliveryDistribution::getInventoryCode,
                        DeliveryDistribution::getOldInventoryCode,
                        DeliveryDistribution::getDistributionQuantity,
                        DeliveryDistribution::getPickingQuantity,
                        DeliveryDistribution::getPickStockOutNum)
                .eq(DeliveryDistribution::getDeliveryCode, deliveryCode));
        log.info("订单取消处理，获取订单【{}】分配明细：{}",deliveryCode,JSONUtil.toJsonStr(deliveryDistributions));

        //根据库存分配记录获取临时库存和原始库存信息
        Map<String, Inventory> inventoryMap =inventoryInfo(deliveryCode,deliveryDistributions,needReturnSourceInventory);
        log.info("订单取消处理，获取订单【{}】分配管理的所有库存信息：{}",deliveryCode,JSONUtil.toJsonStr(inventoryMap));
        //获取订单要减少的临时库存、要增加的原始库存、操作数量
        OperationData cancelQuantityMap = getCancelQuantity(deliveryCode, deliveryDistributions, inventoryMap,warehouseCode,needReturnSourceInventory);
        log.info("订单取消处理，获取订单【{}】要减少的临时库存、要增加的原始库存、操作数量：{}",deliveryCode,JSONUtil.toJsonStr(cancelQuantityMap));

        this.operationInventory(deliveryCode,cancelQuantityMap,inventoryMap);
    }

    /**
     * 根据库存分配记录获取临时库存和原始库存信息
     * @author liuxr
     * @date 2021/11/19 16:52
     * @param deliveryCode 订单号
     * @param deliveryDistributions 订单分配记录
     * @param needReturnSourceInventory 临时库存是否退回原库位，true：退回原库位，false：退回退拣库位
     * @return java.util.Map<java.lang.String,com.arpa.wms.gateway.domain.Inventory> 库存信息 key：库存标识，value：库存信息
     */
    private Map<String, Inventory> inventoryInfo(String deliveryCode,List<DeliveryDistribution> deliveryDistributions ,boolean needReturnSourceInventory){
        //通过分配明细获取所有临时库存标识
        Set<String> tempInventoryCodeSet = deliveryDistributions.parallelStream().map(DeliveryDistribution::getInventoryCode).collect(Collectors.toSet());
        log.info("订单取消处理，获取订单【{}】分配的临时库存标识：{}",deliveryCode,JSONUtil.toJsonStr(tempInventoryCodeSet));
        //合并所有的库存标识
        Set<String> inventoryCodeSet = new HashSet<>(tempInventoryCodeSet);


        if(needReturnSourceInventory) {
            //通过分配明细获取所有原始库存标识
            Set<String> oldInventoryCodeSet = deliveryDistributions.parallelStream().map(DeliveryDistribution::getOldInventoryCode).collect(Collectors.toSet());
            log.info("订单取消处理，获取订单【{}】分配的原库存标识：{}", deliveryCode, JSONUtil.toJsonStr(oldInventoryCodeSet));
            inventoryCodeSet.addAll(oldInventoryCodeSet);
        }

        //通过临时库存、原始库存获取所有库存信息
        List<Inventory> inventories = inventoryService.list(new LambdaQueryWrapper<Inventory>().in(Inventory::getCode, inventoryCodeSet));
        //将库存信息通过库存code转成map
        return inventories.stream().collect(Collectors.toMap(Inventory::getCode, r -> r));
    }

    /**
     * 构造要处理的数据
     * 订单要减少的临时库存、要增加的原始库存、操作数量
     *
     * @param deliveryCode 订单号
     * @param deliveryDistributions 订单分配明细
     * @param inventoryMap 库存信息
     * @param warehouseCode 订单所属仓库
     * @param needReturnSourceInventory 临时库存是否退回原库位，true：退回原库位，false：退回退拣库位
     * @return java.util.Map<java.lang.String, java.lang.Object>
     * @author liuxr
     * @date 2021/6/2 13:38
     */
    private OperationData getCancelQuantity(String deliveryCode, List<DeliveryDistribution> deliveryDistributions, Map<String, Inventory> inventoryMap,String warehouseCode,boolean needReturnSourceInventory) {
        //获取订单要减少的临时库存、要增加的原始库存、操作数量
        OperationData operationData = new OperationData();
        //将分配明细按照临时库存标识分组，计算临时库存需要减少的数量
        //返回 Map<临时库存标识,合计分配数量> 要减少库存的临时库存及数量
        List<InventoryOperationDto> subtract = deliveryDistributions.stream()
                .collect(Collectors.groupingBy(DeliveryDistribution::getInventoryCode))
                .entrySet().stream()
                .map(m -> {
                    InventoryOperationDto dto = new InventoryOperationDto();
                    dto.setUuid(IdUtil.simpleUUID());
                    dto.setInventoryCode(m.getKey());
                    dto.setQuantity(m.getValue()
                            .stream()
                            .map(DeliveryDistribution::getDistributionQuantity)
                            .reduce(BigDecimal.ZERO, BigDecimal::add));
                    dto.setInventory(inventoryMap.get(m.getKey()));
                    return dto;
                })
                .collect(Collectors.toList());
        //所有库存操作记录集合
        List<InventoryRecord> inventoryRecords = new ArrayList<>();
        String groupCode = UserUtil.getBranchCode();

        //创建减库存操作记录
        subtract.forEach(r -> {
            InventoryRecord record = new InventoryRecord();
            BeanUtil.copyProperties(r.getInventory(), record);
            record.setCode(IdUtil.simpleUUID());
            record.setGroupCode(groupCode);
            record.setInventoryCode(r.getInventoryCode());
            record.setSourceCode(deliveryCode);
            record.setNum(r.getQuantity().negate());
            record.setType(InventoryRecordTypeEnum.DELIVERY.getValue());
            record.setRemarks("订单："+deliveryCode+"取消，分配的临时库存退还原库位");
            inventoryRecords.add(record);
        });
        // 接收处理后的数据
        List<InventoryOperationDto> subtractData = getInventoryOperationDtos(subtract);
        operationData.setSubtractData(subtractData);


        if(needReturnSourceInventory) {
            log.info("退回原库存处理");
            //将分配明细按照原始库存标识分组，计算原始库存需要增加的数量
            //返回 Map<原始库存标识,合计分配数量> 要增加库存的原始库存及数量，同时用于拣货单明细更新取消数量用
            List<InventoryOperationDto> add = deliveryDistributions.stream()
                    .collect(Collectors.groupingBy(DeliveryDistribution::getOldInventoryCode))
                    .entrySet().parallelStream()
                    .map(m -> {
                        InventoryOperationDto dto = new InventoryOperationDto();
                        dto.setUuid(IdUtil.simpleUUID());
                        dto.setInventoryCode(m.getKey());
                        dto.setQuantity(m.getValue()
                                .stream()
                                .map(d -> d.getDistributionQuantity().subtract(d.getPickingQuantity()).subtract(d.getPickStockOutNum()))
                                .reduce(BigDecimal.ZERO, BigDecimal::add));
                        dto.setInventory(inventoryMap.get(m.getKey()));
                        return dto;
                    })
                    .collect(Collectors.toList());

            //创建加库存操作记录
            add.forEach(r -> {
                InventoryRecord record = new InventoryRecord();
                BeanUtil.copyProperties(r.getInventory(), record);
                record.setCode(IdUtil.simpleUUID());
                record.setGroupCode(groupCode);
                record.setInventoryCode(r.getInventoryCode());
                record.setSourceCode(deliveryCode);
                record.setNum(r.getQuantity());
                record.setType(InventoryRecordTypeEnum.DELIVERY.getValue());
                record.setRemarks("订单："+deliveryCode+"取消，分配的临时库存退还原库位");
                inventoryRecords.add(record);
            });
            // 接收处理后的数据
            List<InventoryOperationDto> addData = getInventoryOperationDtos(add);
            operationData.setAddData(addData);
        }else{
            log.info("退回退拣库位处理");
            // 获取退减临时库位
            Location reLocation = iLocationService.getOne(Wrappers.lambdaQuery(Location.class)
                    .eq(Location::getGroupCode,UserUtil.getBranchCode())
                    .select(Location::getCode)
                    .eq(Location::getWarehouseCode, warehouseCode)
                    .eq(Location::getSerialNumber, TempLocationTypeEnum.RE));
            if (null == reLocation) {
                log.error("仓库【{}】缺少退减库位", warehouseCode);
                throw new ServiceException("拣货确认失败，仓库缺少退减库位");
            }

            //退减库存
            List<Inventory> reInventorys = new ArrayList<>();
            //创建减库存操作记录
            subtract.forEach(r -> {
                //退减库存信息
                Inventory inventory = new Inventory();
                BeanUtil.copyProperties(r.getInventory(), inventory);
                inventory.setCode(IdUtil.simpleUUID());
                inventory.setUseQuantity(BigDecimal.ZERO);
                inventory.setOldInventoryCode(r.getInventory().getCode());
                inventory.setTempCode(StrUtil.EMPTY);
                inventory.setTempItemCode(StrUtil.EMPTY);
                inventory.setTempRegisterCode(StrUtil.EMPTY);
                inventory.setLocation(reLocation.getCode());
                inventory.setQuantity(r.getQuantity());
                // 抹去容器号等信息
                inventory.setContainerBarCode(StrUtil.EMPTY);
                inventory.setRemarks("订单："+deliveryCode+"取消，已拣商品存放到退拣库位");
                reInventorys.add(inventory);

                //退减库存擦插入操作记录
                InventoryRecord reRecord = new InventoryRecord();
                BeanUtil.copyProperties(inventory, reRecord);
                reRecord.setCode(IdUtil.simpleUUID());
                reRecord.setGroupCode(groupCode);
                reRecord.setInventoryCode(inventory.getCode());
                reRecord.setSourceCode(deliveryCode);
                reRecord.setNum(inventory.getQuantity());
                reRecord.setType(InventoryRecordTypeEnum.OUTBOUND.getValue());
                reRecord.setRemarks("订单："+deliveryCode+"取消，已拣商品存放到退拣库位");
                inventoryRecords.add(reRecord);
            });


            List<Inventory> reInventoryDatas = new ArrayList<>();
            // 数据分组统计处理
            reInventorys.stream()
                    .collect(Collectors.groupingBy(Inventory::getOldInventoryCode, Collectors.toList()))
                    .forEach((inventoryCode, dtos) -> {
                        dtos.stream()
                                .reduce((a, b) -> {
                                    Inventory inventory = new Inventory();
                                    BeanUtil.copyProperties(a, inventory);
                                    inventory.setCode(IdUtil.simpleUUID());
                                    inventory.setQuantity(a.getQuantity().add(b.getQuantity()));
                                    return inventory;
                                })
                                .ifPresent(reInventoryDatas::add);
                    });


            operationData.setReInventoryData(reInventoryDatas);
        }


        operationData.setInventoryRecords(inventoryRecords);
        return operationData;
    }

    /**
     * 更新库存
     *
     * @param data         操作数据
     * @param inventoryMap 原库存集合
     * @author liuxr
     * @date 2021/6/2 16:08
     */
    private void operationInventory(String deliveryCode,OperationData data, Map<String, Inventory> inventoryMap) {
        log.info("订单取消处理，开始处理订单【{}】关联的库存信息",deliveryCode);
        //要减少的临时库存
        List<InventoryOperationDto> subtractData = data.getSubtractData();
        //要增加的原始库存
        List<InventoryOperationDto> addData = data.getAddData();
        //库存操作记录集合
        List<InventoryRecord> inventoryRecords =data.getInventoryRecords();
        //要增加的退拣库存
        List<Inventory> reInventoryData = data.getReInventoryData();

        //减少临时库存
        log.info("订单取消处理，开始处理订单【{}】减少临时库存【{}】",deliveryCode,JSONUtil.toJsonStr(subtractData));
        if(IterUtil.isNotEmpty(subtractData)) {
            inventoryOperationService.subtractBatch(subtractData);
            validated(deliveryCode, subtractData, inventoryMap);
        }
        //批量加库存

        log.info("订单取消处理，开始处理订单【{}】批量加原库存【{}】", deliveryCode, JSONUtil.toJsonStr(addData));
        if(IterUtil.isNotEmpty(addData)) {
            inventoryOperationService.addBatch(addData);
        }

        log.info("订单取消处理，开始处理订单【{}】增加退拣库存【{}】", deliveryCode, JSONUtil.toJsonStr(reInventoryData));
        if(IterUtil.isNotEmpty(reInventoryData)) {
            inventoryService.saveBatch(reInventoryData);
        }


        //批量插入操作记录
        log.info("订单取消处理，开始处理订单【{}】批量插入操作记录【{}】",deliveryCode,JSONUtil.toJsonStr(inventoryRecords));
        if(IterUtil.isNotEmpty(inventoryRecords)) {
            inventoryRecordService.saveBatch(inventoryRecords);
        }

    }

    /**
     * 临时库存操作结果校验
     * @author liuxr
     * @date 2021/11/23 11:08
     * @param deliveryCode 订单号
     * @param subtractData 操作数据
     * @param inventoryMap 源数据
     */
    private void validated(String deliveryCode,List<InventoryOperationDto> subtractData, Map<String, Inventory> inventoryMap){
        log.info("订单取消处理，临时库存操作结果校验,订单号【{}】",deliveryCode);
        //进行库存验证
        //库存操作信息通过库存code转成map
        Map<String, InventoryOperationDto> stringInventoryOperationDtoMap = subtractData.stream().collect(Collectors.toMap(InventoryOperationDto::getInventoryCode, r -> r));

        //更新后的临时库存
        Set<String> inventoryCodes = subtractData.stream().map(InventoryOperationDto::getInventoryCode).collect(Collectors.toSet());
        List<Inventory> newInventorys = inventoryService.list(Wrappers.lambdaQuery(Inventory.class)
                .eq(Inventory::getGroupCode,UserUtil.getBranchCode())
                .select(Inventory::getCode, Inventory::getQuantity)
                .in(Inventory::getCode, inventoryCodes));
        newInventorys.forEach(n -> {
            String inventoryCode = n.getCode();
            //更新前临时库存
            Inventory oldInventory = inventoryMap.get(inventoryCode);
            //要操作的库存数据
            InventoryOperationDto inventoryOperationDto = stringInventoryOperationDtoMap.get(inventoryCode);
            if (oldInventory.getQuantity().compareTo(n.getQuantity().add(inventoryOperationDto.getQuantity())) != 0) {
                log.info("订单取消处理异常，订单【{}】临时库存【{}】扣减失败",deliveryCode,inventoryCode);
                throw new ServiceException("订单取消处理异常，订单【"+deliveryCode+"】临时库存【" + inventoryCode + "】扣减失败");
            }
        });
    }

    @NotNull
    private List<InventoryOperationDto> getInventoryOperationDtos(List<InventoryOperationDto> data) {
        List<InventoryOperationDto> result = new ArrayList<>();
        // 数据分组统计处理
        data.stream()
                .collect(Collectors.groupingBy(InventoryOperationDto::getInventoryCode, Collectors.toList()))
                .forEach((id, transfer) -> {
                    transfer.stream()
                            .reduce((a, b) ->

                            {
                                InventoryOperationDto dto = new InventoryOperationDto();
                                dto.setUuid(IdUtil.simpleUUID());
                                dto.setInventoryCode(a.getInventoryCode());
                                dto.setQuantity(a.getQuantity().add(b.getQuantity()));
                                dto.setInventory(a.getInventory());
                                return dto;
                            })
                            .ifPresent(result::add);
                });
        return result;
    }


    @Data
    class OperationData{
        List<InventoryOperationDto> subtractData ;
        //要增加的原始库存
        List<InventoryOperationDto> addData ;

        List<InventoryRecord> inventoryRecords;

        List<Inventory> reInventoryData;
    }
}
